﻿using Infrastructure.ElasticSearch.Enums;
using Nest;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Infrastructure.ElasticSearch.Nests
{
    public class ElasticSearchProvider<SearchEntity> where SearchEntity : class
    {
        private SearchEntity _entity;

        private List<ElasticSearchItem<SearchEntity>> _searchItems;
        //缓存属性列表
        private static List<PropertyInfo> propertyList = new List<PropertyInfo>();

        public SearchEntity Entity { get { return _entity; } }

        public ElasticSearchProvider(SearchEntity t)
        {
            _entity = t;
            _searchItems = new List<ElasticSearchItem<SearchEntity>>();
            PropertiesCache(t);
        }

        /// <summary>
        /// 添加查询条件
        /// </summary>
        /// <param name="condition"></param>
        /// <param name="field"></param>
        /// <param name="path"></param>
        /// <param name="position"></param>
        /// <param name="opera"></param>
        /// <param name="nestPosition"></param>
        /// <returns></returns>
        public ElasticSearchProvider<SearchEntity> AddQuery(
            Expression<Func<SearchEntity, object>> field,
            QueryOperator opera = QueryOperator.Include,
            QueryPositionEnum position = QueryPositionEnum.Must,
            string path = "",
            QueryPositionEnum nestPosition = QueryPositionEnum.None)
        {
            var memberName = (field.Body as MemberExpression).Member.Name;
            if (!_searchItems.Exists(x => x.OrginFieldName == memberName && x.Path == path && x.Pos == position))
            {
                var itemVal = GetPropertyVal(memberName);
                if (itemVal == null || itemVal.All(x => string.IsNullOrWhiteSpace(x))) return this;

                ElasticSearchItem<SearchEntity> searchItem = null;
                var jsonPropertyAttribute = GetFieldAttribute(memberName, typeof(JsonPropertyAttribute));
                var rangeAttribute = GetFieldAttribute(memberName, typeof(RangeFieldAttribute));
                var termsAttribute = GetFieldAttribute(memberName, typeof(TermsFieldAttribute));
                if (rangeAttribute != null)
                {
                    searchItem = new ElasticSearchItemRangeProperty<SearchEntity>(rangeAttribute as RangeFieldAttribute, field, opera, position, path, nestPosition);
                }
                else if (termsAttribute != null)
                {
                    searchItem = new ElasticSearchItemTermsProperty<SearchEntity>(termsAttribute as TermsFieldAttribute, field, opera, position, path, nestPosition);
                }
                else if (jsonPropertyAttribute != null)
                {
                    searchItem = new ElasticSearchItemJsonProperty<SearchEntity>(jsonPropertyAttribute as JsonPropertyAttribute, field, opera, position, path, nestPosition);
                }
                else
                {
                    searchItem = new ElasticSearchItem<SearchEntity>(field, opera, position, path, nestPosition);
                }

                if (searchItem != null)
                {
                    searchItem.SetItemVal(itemVal);
                    _searchItems.Add(searchItem);
                }
            }

            return this;
        }

        public ElasticSearchProvider<SearchEntity> AddQueryIf(
            bool condition,
            Expression<Func<SearchEntity, object>> field,
            QueryOperator opera = QueryOperator.Include,
            QueryPositionEnum position = QueryPositionEnum.Must,
            string path = "",
            QueryPositionEnum nestPosition = QueryPositionEnum.None)
        {
            if (condition)
            {
                return AddQuery(field, opera, position, path, nestPosition);
            }
            return this;
        }

        private Attribute GetFieldAttribute(string propertyName, Type attributeType)
        {
            var property = propertyList.FirstOrDefault(x => x.Name == propertyName);

            return property != null ? property.GetCustomAttribute(attributeType, true) : null;
        }

        /// <summary>
        /// 生成bool类型query
        /// </summary>
        public BoolQueryDescriptor<SearchEntity> BuildBoolQuery()
        {
            return GetBoolQueryDescriptor();
        }

        public CountDescriptor<SearchEntity> BuildCountQuery()
        {
            var queryContainer = GetBoolQueryDescriptor();
            return new CountDescriptor<SearchEntity>().Query(q => q.Bool(b => BuildBoolQuery()));
        }

        public BoolQueryDescriptor<SearchEntity> GetBoolQueryDescriptor()
        {
            //合并items中的同名的item，比如rangeitem
            MergeItems();

            var queryContainer = new BoolQueryDescriptor<SearchEntity>();

            BuildQueryContainer(queryContainer, _searchItems.Where(x => x.Pos == QueryPositionEnum.Must || x.Pos == QueryPositionEnum.MustShould).ToList(), QueryPositionEnum.Must);
            BuildQueryContainer(queryContainer, _searchItems.Where(x => x.Pos == QueryPositionEnum.MustNot).ToList(), QueryPositionEnum.MustNot);
            BuildQueryContainer(queryContainer, _searchItems.Where(x => x.Pos == QueryPositionEnum.Should).ToList(), QueryPositionEnum.Should);

            return queryContainer;
        }


        /// <summary>
        /// 合并item中同名的数据，目前只有range需要合并
        /// </summary>
        private void MergeItems()
        {
            if (_searchItems == null || !_searchItems.Any() || _searchItems.Count <= 1) return;

            var groupItems = _searchItems.GroupBy(x => new { x.SearchFieldName, x.Path, x.Pos, x.NestPos }).Where(x => x.Count() > 1).ToList();

            foreach (var item in groupItems)
            {
                var next = item.ElementAt(1);
                if (next is ElasticSearchItemRangeProperty<SearchEntity>)
                {
                    (item.ElementAt(0) as ElasticSearchItemRangeProperty<SearchEntity>).SetCompareRange(next as ElasticSearchItemRangeProperty<SearchEntity>);
                    _searchItems.Remove(next);
                }
            }
        }

        /// <summary>
        /// 获取每个模块下的项
        /// </summary>
        /// <param name="items"></param>
        /// <param name="path"></param>
        /// <param name="depth"></param>
        /// <returns></returns>
        private void BuildQueryContainer(BoolQueryDescriptor<SearchEntity> boolQuery, List<ElasticSearchItem<SearchEntity>> items, QueryPositionEnum pos = QueryPositionEnum.Must, string path = "", QueryPositionEnum nestPosition = QueryPositionEnum.None)
        {
            if (items == null || !items.Any()) return;

            List<QueryContainerDescriptor<SearchEntity>> containers = new List<QueryContainerDescriptor<SearchEntity>>();

            #region pos子条件
            var searchItems = items.Where(x => x.Pos == pos && x.NestPos == nestPosition && x.Path == path);
            containers.AddRange(searchItems.Select(s => s.Search()));
            #endregion

            #region pos嵌套条件,如must嵌套should
            var childItems = items.Where(x => x.Pos != pos && x.NestPos == nestPosition && x.Path == path);
            if (childItems != null && childItems.Any())
            {
                var childContainer = new QueryContainerDescriptor<SearchEntity>();
                BoolQueryDescriptor<SearchEntity> childBool = new BoolQueryDescriptor<SearchEntity>();

                if (childItems.First().Pos == QueryPositionEnum.MustShould)
                {
                    SetBoolQuery(childBool, QueryPositionEnum.Should, childItems.Select(s => s.Search()).ToList());
                    childContainer.Bool(b => childBool);
                }

                containers.Add(childContainer);
            }
            #endregion

            var hasPathItems = items.Where(x => !string.IsNullOrWhiteSpace(x.Path));
            if (hasPathItems != null && hasPathItems.Any())
            {
                var groups = hasPathItems.GroupBy(x => x.Path);
                foreach (var group in groups)
                {
                    if (group.Key == path)
                    {
                        SetBoolQuery(boolQuery, nestPosition, containers);  //组织nest中bool查询的must,mustnot,should
                        break;
                    }

                    var container = new QueryContainerDescriptor<SearchEntity>();

                    var nestQuery = new NestedQueryDescriptor<SearchEntity>().Path(group.Key);
                    var nestBoolQuery = new BoolQueryDescriptor<SearchEntity>();

                    BuildQueryContainer(nestBoolQuery, group.Where(x => x.Pos == pos || x.Pos == QueryPositionEnum.MustShould).ToList(), pos, group.Key, QueryPositionEnum.Must);
                    BuildQueryContainer(nestBoolQuery, group.Where(x => x.Pos == pos).ToList(), pos, group.Key, QueryPositionEnum.MustNot);
                    BuildQueryContainer(nestBoolQuery, group.Where(x => x.Pos == pos).ToList(), pos, group.Key, QueryPositionEnum.Should);

                    nestQuery.Query(q => q.Bool(b => nestBoolQuery));
                    container.Nested(n => nestQuery);   //添加到nest中

                    //将nest添加到外层must,mustnot,should中
                    if (string.IsNullOrWhiteSpace(path))
                    {
                        containers.Add(container);

                        SetBoolQuery(boolQuery, pos, containers);
                    }
                }
            }
            else
            {
                SetBoolQuery(boolQuery, pos, containers);
            }
        }

        /// <summary>
        /// 生成boolquery下的查询
        /// </summary>
        /// <param name="boolQuery"></param>
        /// <param name="pos"></param>
        /// <param name="containers"></param>
        private static void SetBoolQuery(BoolQueryDescriptor<SearchEntity> boolQuery, QueryPositionEnum pos, List<QueryContainerDescriptor<SearchEntity>> containers)
        {
            if (containers != null && containers.Any())
            {
                if (pos == QueryPositionEnum.Must)
                    boolQuery.Must(containers.ToArray());
                else if (pos == QueryPositionEnum.MustNot)
                    boolQuery.MustNot(containers.ToArray());
                else if (pos == QueryPositionEnum.Should)
                    boolQuery.Should(containers.ToArray()).MinimumShouldMatch(MinimumShouldMatch.Fixed(1));
            }
        }


        /// <summary>
        /// 缓存属性
        /// </summary>
        /// <param name="entity"></param>
        private void PropertiesCache(SearchEntity entity)
        {
            if (propertyList.Count == 0)
            {
                propertyList.AddRange(_entity.GetType().GetProperties());
            }
        }

        /// <summary>
        /// 获取属性值
        /// </summary>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        protected List<string> GetPropertyVal(string propertyName)
        {
            var property = propertyList.FirstOrDefault(x => x.Name == propertyName);
            object propertyVal = null;
            if (property != null)
            {
                propertyVal = property.GetValue(_entity);
                if (propertyVal == null) return null;

                if (property.PropertyType.Name == nameof(Models.EsMultiSearchModel))
                {
                    if ((propertyVal as Models.EsMultiSearchModel).HasValue)
                    {
                        return (propertyVal as Models.EsMultiSearchModel).Values;
                    }
                }
                else
                {
                    return new List<string> { propertyVal.ToString() };
                }

            }

            return null;
        }

    }
}
